﻿module produceOutput

open System
open System.Linq
open System.IO
open System.Diagnostics
open System.Threading
open tilingTools

// we need to generate the header for the HTML file
let generateHeader = 
    "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" +
    "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" +
    "<head>\n" +
    "<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\" />\n" +
    "<meta charset=\"utf-8\" />\n" +
    "<title>DNA Structure Report - Structurizer Output</title>\n" +
    "</head>\n" +
    "<body>\n" +
    "<h1>Structurizer Output Report</h1>"

// we need to generate the footer for the HTML file
let generateFooter = 
    "<div id=\"footer\" style=\"text-align: center; margin-top: 10px; font-size:x-small;\">\n" + 
    "Produced 2011-2012 by <a href=\"http://dna.eecs.northwestern.edu/\">The Kao Group</a> | <a href=\"http://eecs.northwestern.edu/\">Department of Electrical Engineering \n" + 
    "and Computer Science</a> | <a href=\"http://mccormick.northwestern.edu/\">McCormick School of Engineering</a> | " +
    "<a href=\"http://www.northwestern.edu/\">Northwestern University</a>\n" +
    "</div>\n" +
    "</div>\n" +
    "</body>\n" +
    "</html>"

let detBorder (border: string) =
    let mutable color = ""
    match border.[0] with
        |'A' -> color <- "red"
        |'B' -> color <- "blue"
        |'F' -> color <- "gray"
        |'N' -> color <- "black"
        | _ -> color <- "green"
    color

    (*
    if border.[0] = 'A' then
        color <- "red"
    elif border.[0] = 'B' then
        color <- "blue"
    elif border.[0] = 'F' then
        color <- "gray"
    elif border.[0] = 'N' then
        color <- "black"
    color*)

// we present the shape in the form of an HTML table with each border of each cell symbolizing the glue... so we'll choose colors for:
// A (supertile-border), B (tile-border), F (filler), N (none); we can access each one using fst of each subarray's cells 3-6
// our colors: A = red, B = blue, F = white, N = gray
let produceTableMap (tiles : string [][]) =
    // deduce the number of tiles
    let n = tiles.Length
    // both height and width are absolute, they run from 0 to size
    let width =
        let listX = tiles |> Array.map (fun x -> int x.[1])
        let maxWidth = Array.max listX
        // printf "\tshapeWidth: %d\n" maxWidth
        maxWidth
    let height = 
        let listY = tiles |> Array.map (fun x -> int x.[2])
        let maxHeight = Array.max listY
        // printf "\tshapeHeight: %d\n" maxHeight
        maxHeight
    // let's initialize our loops for creating the table (which we can assume will have the width and height from before)
    // we begin by resorting our tiles
    let sortedTiles = tiles |> Array.sortBy (fun elem -> (-(int elem.[1]),(int elem.[2])))
    // now we can run a correct for loop, since the array goes in order ** FUCK DOES IT EVEN MATTER?
    let mutable outputString = "<h2>Shape/Tile Map</h2>\n<table>\n"
    for i in height .. -1 .. 0 do
        outputString <- outputString + "<tr>\n"
        let findcurrRow = tiles |> Array.filter (fun elem -> ((int elem.[2]) = i))
        let currentRow = findcurrRow |> Array.sortBy (fun elem -> int elem.[1])
        // we really should be using array.map for this next part but it's okay
        // for each tile that is at this height, it's produce the tile set
        //let mutable j = currentRow.Length-1
        let mutable j = 0
        let currX = currentRow |> Array.map (fun x -> int x.[1])
        // printf "\tcurrX = %A\n" currX
        // add 1 because the number is 1 more than the max... as the number system starts from 0
        let rowMax = 1 + (Array.max currX)
        // printf "\trowMax = %d\n" rowMax
        while j < rowMax do
            if j = int currentRow.[j].[1] then
                // N E S W
                let topBorder = "\t<td style=\"border-top: 4px solid " + detBorder currentRow.[j].[3]
                let rightBorder = "; border-right: 4px solid " + detBorder currentRow.[j].[4]
                let bottomBorder = "; border-bottom: 4px solid " + detBorder currentRow.[j].[5]
                let leftBorder = "; border-left: 4px solid " + detBorder currentRow.[j].[6] 
                outputString <- outputString + topBorder + rightBorder + bottomBorder + leftBorder + "; padding: .3em; color: black;\">" + currentRow.[j].[0]
                outputString <- outputString + "</td>\n"
            else
                outputString <- outputString + "\t<td></td>\n" 
            // j <- j-1
            j <- j+1
        done
        outputString <- outputString + "</tr>\n"
    done
    outputString <- outputString + "</table>\n<br /><h4>Glue Type Legend</h4>\n<table>\n<tr>\n<td style=\"color: red; background-color: red; padding: .3em\">RA</td>\n<td>= A-class (supertile bonds)</td>\n<td style=\"color: blue; background-color: blue; padding: .3em\">BB</td>\n<td>= B-class (border-tile bonds)</td>\n<td style=\"color: gray; background-color: gray; padding: .3em\">GF</td>\n<td>= F-class (filler bonds)</td>\n</tr>\n</table>\n"
    outputString

// now that we have the table, let's print each tile and the type of glue needed for each side:
// our schema will be as follows: A blowup of the tile (larger size so the colors are evident) floating left
// coordinates, supertile block number, and finally, the glues (North, East, South, West)

// it'll just be easier to either run loops or map each distinct tile type over
// let's first collect all the glues (each glue, which is a <tr>, will be in one array cell)
//let outputC8BArray n l k1 k2 k3 k4 k5 k6 gamma d =

// we're going to try this with some sample k-values, which should be studied a bit more later
// we need the classA glues to be as strong as classB, maybe even a little stronger

// function for generating complement of the input glue in char[] form.
let genComplement (glueIn : char[]) =
    let mutable glue = Array.rev (Array.copy glueIn)
    for j = 0 to -1+glue.Length do
        if glue.[j] = 'A' then
            glue.[j] <- 'T'
        elif glue.[j] = 'C' then
            glue.[j] <- 'G'
        elif glue.[j] = 'T' then
            glue.[j] <- 'A'
        elif glue.[j] = 'G' then
            glue.[j] <- 'C'
    done
    glue

let createTable (finalB : char [,]) =
    let p = finalB.GetLength(0) // length
    let q = finalB.GetLength(1) // quantity
    let mutable outArray = [||]
        
    // for each glue, we'll create a cell in the array instead
    for i = 0 to (q-1) do
        let mutable tableString = "<tr><td>G" + string (i+1) + ": &nbsp; 3'</td>"
        // **** since this is the loop for each glue, maybe it would be wise to just do a row copy and reversing here...
        // BOSS: SINCE WE HAVE THE COMPLEMENT OF THE GLUE NOW (FINALLY) WE CAN USE IT IN SOME WAY (maybe as the latter half of the string[]?)

        // *** SEE ID idea below, maybe modify wordtoGlue to take the complement, which will need to be generated in a different loop here:
        let currGlue = Array.init p (fun d -> finalB.[d,i])
        let compGlue = genComplement currGlue
        for j = 0 to (p-1) do
            if currGlue.[j] = 'A' then
                tableString <- tableString + "<td style=\"background-color: #5050ff; padding: .3em; color: black;\">" + "A" + "</td>"
            elif currGlue.[j] = 'C' then
                tableString <- tableString + "<td style=\"background-color: #e00000; padding: .3em; color: black;\">" + "C" + "</td>"
            elif currGlue.[j] = 'T' then
                tableString <- tableString + "<td style=\"background-color: #e6e600; padding: .3em; color: black;\">" + "T" + "</td>"
            elif currGlue.[j] = 'G' then
                tableString <- tableString + "<td style=\"background-color: #00c000; padding: .3em; color: black;\">" + "G" + "</td>"
        done
        tableString <- tableString + "<td>5'</td></tr>"
        // Array schema: [wordId(string int); tableCells(string)], this is an array within outArray.
        // so for schema, should it be for complements, wordId = (string i) + "c"?
        // sorting will just need to be done on a different level, but i think since we know the number of words we are obtaining, we can get away with
        // just looking for the ID of the array that matches the glue we are looking for?
        //outArray <- Array.append outArray [|string i;tableString|]
        outArray <- Array.append outArray [|tableString|]

        // let's make the array for the complement glue now **FUTURE UPGRADE TO PATTERNMATCHING
        let mutable compString = "<tr><td>G" + string (i+1) + "c: &nbsp; 3'</td>"
        for j = 0 to (p-1) do
            if compGlue.[j] = 'A' then
                compString <- compString + "<td style=\"background-color: #5050ff; padding: .3em; color: black;\">" + "A" + "</td>"
            elif compGlue.[j] = 'C' then
                compString <- compString + "<td style=\"background-color: #e00000; padding: .3em; color: black;\">" + "C" + "</td>"
            elif compGlue.[j] = 'T' then
                compString <- compString + "<td style=\"background-color: #e6e600; padding: .3em; color: black;\">" + "T" + "</td>"
            elif compGlue.[j] = 'G' then
                compString <- compString + "<td style=\"background-color: #00c000; padding: .3em; color: black;\">" + "G" + "</td>"
        done
        compString <- compString + "<td>5'</td></tr>"
        //outArray <- Array.append outArray [|(string i)+"c";compString|]
        outArray <- Array.append outArray [|compString|]

    done
    outArray

// **** OUR PROBLEM WITH THIS IS THAT WE DONT HAVE ANY OPTIMIZED VALUES, WE WILL NEED TO OBTAIN THEM
// update 11/9: I think I was able to get some optimized values, these should work fine meanwhile
// TODO: figure out where my optimized values went
let getAglues (nA : int) (length : int) =
    let Aglues = dnaTools.outputC8AArray nA length 5 5 5 5 5 5 0.8 5
    let glueTableArray = createTable Aglues
    glueTableArray
let getBglues (nB : int) (length : int) = 
    //let Bglues = dnaTools.outputC8AArray nB length 5 5 5 5 5 5 0.7 4 // d=5 
    // n l k1 k2 k3 k4 k5 k6 y
    let Bglues = BreakRuns.RunBRArray nB length 5 5 5 5 5 0.8 // d=5 
    let glueTableArray = createTable Bglues
    glueTableArray
let getFglues (nF : int) (length : int) = 
    let Fglues = dnaTools.outputC8AArray nF length 5 5 5 5 5 5 0.5 3 // d=6
    let glueTableArray = createTable Fglues
    glueTableArray

// now that we have the glues, let's process them and map it to each tile that requires each glue
// we can do this easier by either looping or mapping (probably looping) each tile, looking at the glueType then integer count
// and seeing if it is the complementary or standard glue.
let wordtoGlue (glue : string) (classAglues : string []) (classBglues : string []) (classFglues : string []) = 
    let mutable outputString = ""
    // printf "\tglue = %s\n" glue
    if glue.[0] = 'A' then
        let ident = int (new System.String(glue.Where(Char.IsDigit).ToArray()))
        // printf "\t\tident = %d\n" ident
        // do we need to use the complementary glue?
        if glue.[glue.Length-1] = 'c' then
            outputString <- classAglues.[1+ident*2]
        else
            outputString <- classAglues.[ident*2]
    elif glue.[0] = 'B' then
        let ident = int (new System.String(glue.Where(Char.IsDigit).ToArray()))
        // printf "\t\tident = %d\n" ident
        if glue.[glue.Length-1] = 'c' then
            outputString <- classBglues.[1+ident*2]
        else
            outputString <- classBglues.[ident*2]
    elif glue.[0] = 'F' then
        let ident = int (new System.String(glue.Where(Char.IsDigit).ToArray()))
        // printf "\t\tident = %d\n" ident
        if glue.[glue.Length-1] = 'c' then
            outputString <- classFglues.[1+ident*2]
        else
            outputString <- classFglues.[ident*2]
    elif glue.[0] = 'N' then
        outputString <- "<tr><td>No glue exists on this side.</td></tr>"
    outputString

// let's try it by nesting tables:
let reportTiles (tiles : string [][]) (classAglues : string []) (classBglues : string []) (classFglues : string []) =
    let beginReport = "<h2>Tiles needed:</h2>"
    let mutable tableString = "<table>"
    for i = 0 to tiles.Length-1 do
        tableString <- tableString + "<tr style=\"margin-bottom: 10px;\">"

        // left side td
        let topBorder = "\t<td style=\"border-top: 4px solid " + detBorder tiles.[i].[3]
        let rightBorder = "; border-right: 4px solid " + detBorder tiles.[i].[4]
        let bottomBorder = "; border-bottom: 4px solid " + detBorder tiles.[i].[5]
        let leftBorder = "; border-left: 4px solid " + detBorder tiles.[i].[6]
        let leftSidetd = "<td style=\"border-bottom: thin solid black;\"><table><tr>\n" + topBorder + rightBorder + bottomBorder + leftBorder + "; padding: .3em; color: black;\">" + tiles.[i].[1] + "," + tiles.[i].[2] + "</td></tr></table></td>\n"

        // right side td
        let mutable rightSidetd = "<td style=\"border-bottom: thin solid black;\">"
        for j = 3 to 6 do
            // let's report the side before writing the nested table
            let mutable reportSide = ""
            if j = 3 then
                reportSide <- reportSide + "North: "
            elif j = 4 then
                reportSide <- reportSide + "East: "
            elif j = 5 then
                reportSide <- reportSide + "South: "
            elif j = 6 then
                reportSide <- reportSide + "West: "
            // now let's write the nested word table
            let mutable glueString = "<table>"
            let wordString = wordtoGlue tiles.[i].[j] classAglues classBglues classFglues
            glueString <- glueString + wordString + "</table>"
            rightSidetd <- rightSidetd + reportSide + glueString
        done
        rightSidetd <- rightSidetd + "</td>"
        tableString <- tableString + "\n" + leftSidetd + "\n" + rightSidetd + "\n</tr>\n"
    done
    tableString <- beginReport + tableString + "</table>\n"
    tableString

// let's use a wrapper to form the report, making sure:
let reportWrapper (tilesWithInfo : string [][]) (lengthA : int) (lengthB : int) (lengthF : int)  =
    // generic XHTML constants
    tilingTools.conTextAdd "generating XHTML header and footer...\n"
    let header = generateHeader
    let footer = generateFooter

    // process incoming data
    tilingTools.conTextAdd "generating glue constants and reformatting tiles..."
    let stats = tilesWithInfo.[tilesWithInfo.Length-1]
    let nA = int tilesWithInfo.[(tilesWithInfo.Length-1)].[2]
    tilingTools.conTextAdd ("\tnumber of class A glues: "+string nA)
    let nB = int tilesWithInfo.[(tilesWithInfo.Length-1)].[4]
    tilingTools.conTextAdd ("\tnumber of class B glues: "+string nB)
    let nF = int tilesWithInfo.[(tilesWithInfo.Length-1)].[6]
    tilingTools.conTextAdd ("\tnumber of class F glues: "+string nF)
    let tiles = tilesWithInfo.[0..(tilesWithInfo.Length-2)]

    // get first part of report
    tilingTools.conTextAdd "generating tile map..."
    let tableMap = produceTableMap tiles

    let mutable opt_lengthA = lengthA
    let mutable opt_lengthB = lengthB
    let mutable opt_lengthF = lengthF

    if ((lengthA = 0)&&(lengthB = 0)&&(lengthF = 0)) then
        // we need to process these optimized Lengths if required
        let deltas = [| 3; 3; 2 |] // 3 5 6
        let numbers = [| nA; nB; nF |]
        let mutable opt_lengths = Array.zeroCreate 3

        // run a loop to calculate the new lengths
        for i = 0 to 2 do
            // calculate the optimized length
            let c1 = FSvars.c1 (float deltas.[i])
            let c_star = FSvars.c_star c1
            let c2 = FSvars.c2 c1 c_star
            let k = FSvars.k 3 3 // 5 and 5
            let l_star = FSvars.l_star (float numbers.[i]) (float k) c1 c2
            let l_opt = ExpCount.findOptimalL numbers.[i] l_star 3 3 // 5 5
            opt_lengths.[i] <- l_opt
        done
        opt_lengthA <- opt_lengths.[0]
        opt_lengthB <- opt_lengths.[1]
        opt_lengthF <- opt_lengths.[2]

    // let's get our glues
    tilingTools.conTextAdd "obtaining glues..."
    let classAglues = getAglues nA opt_lengthA
    let classBglues = getBglues nB opt_lengthB
    let classFglues = getFglues nF opt_lengthF

    // let's make a way to report the glues
    let reportGlues = 
        // printf the %As
        let mutable report = "<div><h2>Glues Needed:</h2><h3>Class A Glues:</h3><table>"
        // fix this ToString, it doesn't actually work
        report <- report + (classAglues |> String.concat "") + "</table><h3>Class B Glues:</h3><table>" + (classBglues |> String.concat "") + "</table><h3>Class F Glues:</h3><table>" + (classFglues |> String.concat "") + "</table></div>"
        report

    // get second part of report
    tilingTools.conTextAdd "generating tile report...\n"
    let tileReport = reportTiles tiles classAglues classBglues classFglues

    tilingTools.conTextAdd "assembling it all together...\n"
    // let's remove reportGlues until i can get it working: reportGlues + 
    let finalOutput = header + tableMap + reportGlues + tileReport + footer
    finalOutput

// **** CODE BELOW IS JUST FOR F# TESTING, THE FRONT-END HAS ITS OWN TIMER/WRAPPER
// we output what we've written to a file... 
// we set filename as this format: @"C:\Users\Wang\Desktop\2011fall\structureReport.html"
let outputReport (tilesWithInfo : string [][]) (filename : string) (lengthA : int) (lengthB : int) (lengthF : int) = 
    let stopWatch = Stopwatch.StartNew()
    let (path : string) = "@\"" + filename
    File.WriteAllText(@""+filename, reportWrapper tilesWithInfo lengthA lengthB lengthF)
    stopWatch.Stop() 
    tilingTools.conTextAdd (">> Report generated in: "+(string stopWatch.Elapsed.TotalMilliseconds)+"milliseconds")